package MT::XPlugin;
use strict;

use vars qw( $VERSION );
$VERSION = 0.45;

use base qw( Class::Accessor::Fast MT::Plugin);

use MT::Request;

my @accessors = qw( key name author_name author_link plugin_link version
  schema_version config_link doc_link description envelope
  system_config_template blog_config_template config_template
  configmgr_class configmgr_system_class icon object_classes
  upgrade_functions );

__PACKAGE__->mk_accessors(@accessors);

# upgrade_functions as a callback chain? seems
# upgrade_functions could be better automated/abstracted.

{

    # MT requires all plugins to descend from MT::Plugin in order to be
    # listed in weblog plugin settings. Most unfortunate. Remove the cruft to
    # avoid issues.
    local $SIG{__WARN__} = sub { };
    *_getset                = sub { };
    *_getset_translate      = sub { };
    *settings               = sub { };
    *config_vars            = sub { };
    *apply_default_settings = sub { };
    *set_config_value       = sub { };
    *get_config_hash        = sub { };
    *get_config_value       = sub { };
    *callbacks              = sub { };
    *junk_filters           = sub { };
    *text_filters           = sub { };
    *template_tags          = sub { };
    *conditional_tags       = sub { };
    *log_classes            = sub { };
    *container_tags         = sub { };
    *global_filters         = sub { };
    *app_methods            = sub { };
    *app_action_links       = sub { };
    *app_itemset_actions    = sub { };
    *tasks                  = sub { };

    # We also benefit from these MT::Plugin methods
    # *load_tmpl = \&MT::Plugin::load_tmpl;
    # sub translate { }
    # sub translate_templatized { }
    # sub l10n_filter { }
    # needs_upgrade { }

}

sub full_path { $_[0]->{full_path} }
sub signature { $_[0]->{plugin_sig} }

sub new {
    my $class = shift;
    my ($self) = ref $_[0] ? @_ : {@_};
    bless $self, $class;
    $self->{configmgr_class} = $class . '::ConfigMgr'
      unless ($self->{configmgr_class} || $class eq 'MT::XPlugin');
    $self->{configmgr_system_class} = $self->{configmgr_class} . '::System'
      unless ($self->{configmgr_system_class} || $class eq 'MT::XPlugin');
    $self->init;
    $self->{__cache_key} = "${class}::cfgmgr";    # harmless if no settings
    $self;
}

sub instance { MT->request->stash($_[0]) }

sub init {
    my $plugin = shift;
    MT->config->PluginSchemaVersion({})
      unless MT->config->PluginSchemaVersion;     # patches case 35604
    $plugin->SUPER::init(@_);
}

sub init_app { }

sub init_request {
    MT->request->stash(ref $_[0], $_[0]) if ref $_[0] ne 'MT::XPlugin';
}

sub init_tasks {
    MT->request->stash(ref $_[0], $_[0]) if ref $_[0] ne 'MT::XPlugin';
}    # use Module::Pluggable eventually.

sub load_config {
    my $plugin = shift;
    my ($param, $scope) = @_;
    my $cfgmgr = $plugin->config_mgr($scope) || return;
    return
      if ref $cfgmgr eq 'MT::PluginData';  # settingless plugin. see note below.
    %$param = %{$cfgmgr->data};            # clone it.
    foreach my $key ($cfgmgr->config_keys) {
        next unless exists $param->{$key};
        my $value = $cfgmgr->$key;
        $param->{$key} = $value;
        next
          if (!defined $value or $value =~ m/\s/ or length($value) > 100)
          ;    # what about illegal characters in this context?
        $param->{$key . '_' . $value} = 1;
    }
    $param->{is_system_scope} = !$scope || $scope eq 'system' ? 1 : 0;
}

sub save_config {
    my $plugin = shift;
    my ($param, $scope) = @_;
    my $cfgmgr = $plugin->config_mgr($scope) || die $plugin->errstr;
    map { $cfgmgr->$_($param->{$_}) }
      $cfgmgr->config_keys;    # what if param is undefined or does not exist???
    $cfgmgr->save_config or $plugin->error($cfgmgr->errstr);
}

sub reset_config {
    my $plugin = shift;
    my $cfgmgr = $plugin->config_mgr($_[0]) || return;
    my $id     = $cfgmgr->{__id};
    if ($cfgmgr && defined $id) {
        delete $plugin->{__cfgmgr}->{$id};
        $cfgmgr->reset_config;
    }
    1;
}

sub config_template {
    my $plugin = shift;
    my ($param, $scope) = @_;
    $scope ||= 'system';
    $scope =~ s/:.*//;
    my $tmpl =
        $scope eq 'system'
      ? $plugin->system_config_template
      : $plugin->blog_config_template;
    unless ($tmpl) {
        $tmpl = '';    # MT doesn't like undef returns
        my $mt = MT->instance;
        my $dir = File::Spec->catdir($mt->mt_dir, $plugin->envelope, 'tmpl');
        if (-d $dir) {
            if ($scope eq 'system') {
                $tmpl = _find_config_tmpl_file($dir, 'system_settings');
                $plugin->system_config_template($tmpl);
            } else {
                $tmpl = _find_config_tmpl_file($dir, 'blog_settings');
                $plugin->blog_config_template($tmpl);
            }
            unless ($tmpl) {
                $tmpl = _find_config_tmpl_file($dir, 'settings');
                $plugin->system_config_template($tmpl);
                $plugin->blog_config_template($tmpl);
            }
        }
    }
    return undef unless $tmpl;
    ref $tmpl eq 'CODE' ? $tmpl->($plugin, @_)
      : $tmpl =~ /\s/ ? $tmpl
      : $plugin->load_tmpl($tmpl);
}

sub _find_config_tmpl_file {
    my ($dir, $key) = @_;
    my $mt4 = MT->version_number >= 4;
    my $tmpl = $mt4 && -e File::Spec->catfile($dir, "${key}.mtml")    # explicit mt4
      ? "${key}.mtml"
      : -e File::Spec->catfile($dir, "${key}.tmpl") ? "${key}.tmpl"
      : '';
    return $tmpl;
}

sub config_mgr {
    my $plugin = shift;
    my $scope = shift || 'system';
    my ($id) = $scope eq 'system' ? 0 : $scope =~ /:(\d+)$/;
    my $cache = MT->request->stash($plugin->{__cache_key})
      || MT->request->stash($plugin->{__cache_key}, {});
    return $cache->{$id} if $cache->{$id};
    my $cfgmgr;
    if ($scope eq 'system') {    # load system specific object if any
        my $class = $plugin->{configmgr_system_class};
        eval "use $class;";
        unless ($@) {
            $cfgmgr = $class->read_config($id)
              || return $plugin->error($class->errstr);
        }
    }
    unless ($cfgmgr) {           # load plugin config if not system
        my $class = $plugin->{configmgr_class};
        eval "use $class;";
        return $plugin->error("Error loading $class: $@") if $@;
        $cfgmgr = $class->read_config($id)
          || return $plugin->error($class->errstr);
    }
    $cache->{$id} = $cfgmgr;
    $cfgmgr;
}

sub l10n_class {
    $_[0]->{'l10n_class'} = $_[1] if $_[1];    # set
    unless (my $class = $_[0]->{'l10n_class'}) {    # get from convention...
        $class ||= ref $_[0] ? ref $_[0] : $_[0];
        $class .= '::L10N';
        eval "require $class";
        $class = 'MT::L10N' if $@;                  # ...else default.
        $_[0]->{'l10n_class'} = $class;
    }
    $_[0]->{'l10n_class'};
}

sub get_config_obj
{    # MT will call this so to keep it happy we have this kludge.
    my $plugin = shift;
    my $mgr    = $plugin->config_mgr(@_);
    return $mgr if $mgr;
    require MT::PluginData;
    my $pdo = MT::PluginData->new;
    $pdo->plugin($plugin->{configmgr_class});
    $pdo->key($plugin->key);
    $pdo->data({});
    $pdo;
}

1;

__END__

=head1 NAME

MT::XPlugin - a base class for creating sophisticated plugin
objects that enabled convention over configuration in an
object-oriented way.

=head1 VERSION

0.45

=head1 SYNOPSIS

=head1 DESCRIPTION

=head1 METHODS

=head1 AUTHOR & COPYRIGHT

MT::XPlugin is Copyright 2005-2006, Timothy Appnel, Appnel
Internet Solutions, LLC. http://appnel.com/

This program is free software; you can use, modify, and
redistribute it under the same terms as Perl 5.8.x itself.

See http://www.perl.com/perl/misc/Artistic.html

=cut
